package session

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sync"
	"time"
)

// Session接口，设置值、读取值、删除值、获取当前SessionID
type Session interface {
	Set(key, value interface{}) error // set session value
	Get(key interface{}) interface{}  // get session value
	Delete(key interface{}) error     // delete session value
	SessionId() string                // back current sessionID
}

// Provider接口用以表征session管理器底层存储结构
type Provider interface {
	SessionInit(sid string) (Session, error) // 实现Session的初始化，操作成功则返回此新的Session变量
	SessionRead(sid string) (Session, error) // 返回sid所代表的Session变量，如果不存在，那么将以sid为参数调用SessinInit函数创建并返回一个新的Session变量
	SessionDestory(sid string) error         // 销毁sid对应的Session变量
	SessionGC(maxLifeTime int64)             // 根据maxLifeTime来删除过期的数据
}

// 定义一个全局的session管理器
type Manager struct {
	cookieName  string     // private cookiename
	lock        sync.Mutex // protects session
	provider    Provider
	maxlifetime int64
}

var provides = make(map[string]Provider)

// 管理器构造函数
func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) {
	provider, ok := provides[provideName]
	if !ok {
		return nil, fmt.Errorf("Session: unknown provide %q (forgotten import?)", provideName)
	}
	return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
}

// 注册存储session的结构
func Register(name string, provider Provider) {
	if provider == nil {
		panic("session:Register provider is nil")
	}
	if _, dup := provides[name]; dup {
		panic("session:Register called twice for provider " + name)
	}
	provides[name] = provider
}

// 全局唯一的Session ID
func (m *Manager) sessionId() string {
	b := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		return ""
	}
	return base64.URLEncoding.EncodeToString(b)
}

// Session创建
func (m *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
	m.lock.Lock()
	defer m.lock.Unlock()
	cookie, err := r.Cookie(m.cookieName)
	if err != nil || cookie.Value == "" {
		sid := m.sessionId()
		session, _ = m.provider.SessionInit(sid)
		cookie := http.Cookie{Name: m.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(m.maxlifetime)}
		http.SetCookie(w, &cookie)
	} else {
		sid, _ := url.QueryUnescape(cookie.Value)
		session, _ = m.provider.SessionRead(sid)
	}
	return
}

// session 重置
func (m *Manager) SessionDestory(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie(m.cookieName)
	if err != nil || cookie.Value == "" {
		return
	} else {
		m.lock.Lock()
		defer m.lock.Unlock()
		m.provider.SessionDestory(cookie.Value)
		expiration := time.Now()
		cookie := http.Cookie{Name: m.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
		http.SetCookie(w, &cookie)
	}
}

// Session 销毁
func (m *Manager) GC() {
	m.lock.Lock()
	defer m.lock.Unlock()
	m.provider.SessionGC(m.maxlifetime)
	time.AfterFunc(time.Duration(m.maxlifetime), func() { m.GC() })
}
